package com.yj.servlet;

import cn.hutool.core.map.multi.AbsCollValueMap;
import cn.hutool.json.JSONUtil;
import com.yj.util.ClassPathXmlResource;
import com.yj.util.XmlConfigReader;
import com.yj.web.MappingValue;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * tomcat 容器初始化的时候会加载该 servlet，并调用 init 方法，在该方法中会加载配置文件，解析配置文件，并初始化 servlet 的映射关系。
 *  1、创建一个ServletContext对象，并将其作为成员变量保存到 servlet 中。
 *  2、创建一个ServletConfig对象，并将其作为成员变量保存到 servlet 中。
 *  3、从配置文件中读取 contextConfigLocation 配置项，并创建 ClassPathXmlResource 对象，该对象会读取配置文件。
 *  4、解析配置文件，并将解析结果保存到 mappingValues 成员变量中。
 *  5、遍历 mappingValues 成员变量，并根据 className 反射出类对象，并将其保存到 mappingClz 成员变量中。
 *  6、遍历 mappingValues 成员变量，并根据 uri 反射出类对象，并将其保存到 mappingObj 成员变量中。
 *  7、在 doGet 方法中，根据请求的 uri 找到对应的 MappingValue 对象，并根据 MappingValue 对象中的 className 和 methodName 反射出类对象中的方法对象，并调用该方法，并将结果返回给客户端。
 *  8、在 determineParameterType 方法中，根据请求参数的值，判断参数的类型，并将参数类型保存到 parameterType 数组中。
 */
public class DispatcherServlet extends HttpServlet {


    Map<String, MappingValue> mappingValues = new HashMap<>();

    Map<String, Class<?>> mappingClz = new HashMap<>();

    Map<String, Object> mappingObj = new HashMap<>();


    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        ServletContext servletContext = config.getServletContext();
        // 加载配置文件
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        if (contextConfigLocation == null) {
            throw new ServletException("contextConfigLocation not specified");
        }
        ClassPathXmlResource resource = new ClassPathXmlResource(contextConfigLocation, servletContext);

        // 解析配置文件
        XmlConfigReader xmlConfigReader = new XmlConfigReader();

        mappingValues = xmlConfigReader.loadConfig(resource);

        refreshMapping();
    }

    private void refreshMapping() {
        for (Map.Entry<String, MappingValue> entry : mappingValues.entrySet()) {
            String uri = entry.getKey();
            MappingValue mappingValue = entry.getValue();
            String className = mappingValue.getClassName();
            Class<?> clz = mappingClz.get(className);
            if (clz == null) {
                try {
                    clz = Class.forName(className);
                    mappingClz.put(className, clz);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
            Object obj = mappingObj.get(uri);
            if (obj == null) {
                try {
                    obj = clz.newInstance();
                    mappingObj.put(uri, obj);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String uri = req.getRequestURI();

        MappingValue mappingValue = mappingValues.get(uri);
        if (mappingValue == null) {
            throw new ServletException("no mapping for uri: " + uri);
        }

        Class<?> clz = mappingClz.get(mappingValue.getClassName());
        Object obj = mappingObj.get(uri);
        String methodName = mappingValue.getMethodName();
        // 参数名字
        Enumeration<String> parameterNames = req.getParameterNames();
        // 参数值
        Map<String, String[]> parameterMap = req.getParameterMap();

        // 请求参数类型
        Class<?>[] parameterType = new Class[parameterMap.size()];

        determineParameterType(parameterMap, parameterType);

        try {
            Method method = clz.getMethod(methodName, parameterType);
            Object result = method.invoke(obj, parameterMap.values().toArray());

            resp.getWriter().write(JSONUtil.toJsonStr(result));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    private void determineParameterType(Map<String, String[]> parameterMap, Class<?>[] parameterType) {

        for (int i = 0; i < parameterType.length; i++) {
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                String key = entry.getKey();
                String[] value = entry.getValue();

                String value0 = value[0];
                if (value0.matches("\\d+")) {
                    parameterType[i] = int.class;
                }else if (value0.matches("\\d+.\\d+")){
                    parameterType[i] = double.class;
                }else{
                    parameterType[i] = String.class;
                }
            }
        }
    }
}
